/**
 * @(#)PursuitGame.java
 *
 *
 * @author Duyun Chen
 * @version 1.00 2008/10/11
 */
 
package Pursuit_Client;

import java.io.*;
import java.util.*;
import java.net.*;

public class PursuitGame implements Runnable 
{
	private String serverIP = "127.0.0.1";
	private int serverPort = 1337;
	private String playerName = "vokuheila";
	private String gameName = "test";
	private final String gameType = "pursuitv0.0";
	private PursuitPlayer player = null;
	private PursuitClient client;
	private Thread game;
	 //Determines whether the game will be run in observer mode
	private boolean observerMode = false;
	
	/**
	 * Creates an instance of pursuit game coupled to a client
	 * @param c The PursuitClient that the Game should be coupled to.
	 */
	public PursuitGame(PursuitClient c) 
	{
		super();
		client = c;
    }
    
	/**
	 * //Will begin the game. Do not call more than once.
	 */
    public void start() 
    {	(game = new Thread(this)).start();  }
    
    public void interrupt() 
    {  	game.interrupt();  }
    
    public void run() 
    {	//main looping method. Implements Runnable
    	if (Thread.interrupted()) 
    		return;
    	
    	if (!observerMode) 
    	{	//If not in observer mode, grab the selected algorithm from list
    		PursuitAlgorithmListPanel.Algorithm a = PursuitAlgorithmListPanel.getSelectedItem();
    	 	player = a.getPlayer();
    	 	
    	 	//System.out.println(player.getClass().getName());
    	 	if (player.getClass().getName().equals("Pursuit_Client.PursuitManualChasee")) 
    	 		client.addKeyListener(client.getAlgorithmListPanel().getDefaultChasee());
    	 	if (player.getClass().getName().equals("Pursuit_Client.PursuitManualChaser")) 
    	 		client.addKeyListener(client.getAlgorithmListPanel().getDefaultChaser());
    	}
    	
    	//create I/O Tools
    	BufferedReader inputFromServer = null;	
    	PrintWriter outputToServer = null;
		Socket pursuitSocket = null;
    	
    	//create socket
    	try 
    	{	pursuitSocket = new Socket(serverIP, serverPort); 	}
    	catch (IOException ioe) 
    	{
    		PursuitClient.print("Error: Cannot connect to port " + Integer.toString(serverPort) + " on " + serverIP);
    		return;
    	}
    	
    	PursuitClient.print("Connected to server " + serverIP + " on port " + Integer.toString(serverPort));

		//Create input stream that sends control string to the server
		//Create output stream that accepts information from the server
		try 
		{
			inputFromServer = new BufferedReader(new InputStreamReader(pursuitSocket.getInputStream()));
			outputToServer = new PrintWriter(pursuitSocket.getOutputStream(), true);
		}
		catch (Exception e) 
		{	PursuitClient.print(e.getMessage());	}

		//print player name to server
		outputToServer.print("{" + playerName + "}");
		outputToServer.flush();
		PursuitClient.print("Sent player name: " + playerName);		
		
		//get server response (game list at this point)
		String ServerResponse = ServerRead(inputFromServer);
		
		//send game name, game type, and role to server
		String InitialControlString = null;
		if (!observerMode)
			InitialControlString = "{" + gameName + "," + gameType + "," + player.getRole() + "}";
		else 
			InitialControlString = "{" + gameName + "," + gameType + ",observer}";
		
		outputToServer.print(InitialControlString);
		outputToServer.flush();
		PursuitClient.print("Sent data: " + InitialControlString);

		//Start communication loop with server
		if (!observerMode)
			PursuitClient.print("Waiting for " + (player.getRole().equals("chaser")? "chasee":"chaser") + " to join...");
		else
			PursuitClient.print("Waiting for chaser and chasee to join...");			

		while(true) 
		{
			client.requestFocus(); //make sure the client is in focus (for manual algorithms)
			
			//read from server and split into separate fields stored in arraylist.
			ServerResponse = ServerRead(inputFromServer);
			ArrayList<String> response = splitString(ServerResponse, ',', 1, ServerResponse.length() - 1);
			
			if (response.size() == 1) 
			{ //if response only has one value, it has to be the end game string (win or lose).
				if (!observerMode) 
					PursuitClient.print("Game ended, you " + response.get(0));
				else 
					PursuitClient.print("Game ended, " + ((response.get(0).equals("hit"))?"chaser ":"chasee ") + "won.");
				client.getGraphicsPanel().stop();
				break;
			}

			double prevThetaHim = 0;

			try 
			{
				//update the graphics panel with new coordinates to draw new line segments
				if (!observerMode) 
				{ //Not observer mode
					
					client.getGraphicsPanel().updateCoordinates(	Double.parseDouble(response.get(0)),	//me.x
																	Double.parseDouble(response.get(1)),	//me.y
																	Double.parseDouble(response.get(6)),	//him.x
																	Double.parseDouble(response.get(7)));	//him.y
				}
				else 
				{ //Observer Mode
					client.getGraphicsPanel().updateCoordinates(	Double.parseDouble(response.get(2)),	//me.x
																	Double.parseDouble(response.get(3)),	//me.y
																	Double.parseDouble(response.get(10)),	//him.x
																	Double.parseDouble(response.get(11)));	//him.y				
				}
			}
			catch (NumberFormatException e)
			{	PursuitClient.print("Error in PursuitGame.run: " + e.getMessage());	}
			
			
			if (!observerMode) 
			{ //debug information
				PursuitClient.print("My X: " + response.get(0));
				PursuitClient.print("My Y: " + response.get(1));
				PursuitClient.print("His X: " + response.get(6));
				PursuitClient.print("His Y: " + response.get(7));
				PursuitClient.print("His delta theta: " + Double.toString(Double.parseDouble(response.get(8)) - prevThetaHim));
				
				prevThetaHim = Double.parseDouble(response.get(8));
			}
			
			//if the graphics panel has not begun at this point, start it.
			if (!(client.getGraphicsPanel().isActive())) 
				client.getGraphicsPanel().begin();
			
			if (!observerMode) 
			{
				try
				{
					
		
				//send data from server into algorithm	
				player.update(	Double.parseDouble(response.get(0)),	//me.x
								Double.parseDouble(response.get(1)),	//me.y
								Double.parseDouble(response.get(2)),	//me.theta
								Double.parseDouble(response.get(3)),	//me.u
								Double.parseDouble(response.get(4)),	//me.v
								Double.parseDouble(response.get(5)),	//me.w
								Double.parseDouble(response.get(6)),	//him.x
								Double.parseDouble(response.get(7)),	//him.y
								Double.parseDouble(response.get(8)),	//him.theta
								Double.parseDouble(response.get(9)),	//him.u
								Double.parseDouble(response.get(10)),	//him.v
								Double.parseDouble(response.get(11)));	//him.w
				}
			
			catch (NumberFormatException e)
			{
				PursuitClient.print("Error in PursuitGame.run: " + e.getMessage());
			}
			}
			
			PursuitDebugger debug = client.getDebugger();
			debug.updateField("My X", response.get(0));
			debug.updateField("My Y", response.get(1));
			debug.updateField("My Theta", response.get(2));
			debug.updateField("My U", response.get(3));
			debug.updateField("My V", response.get(4));
			debug.updateField("My Omega", response.get(5));
			debug.updateField("His X", response.get(6));
			debug.updateField("His Y", response.get(7));
			debug.updateField("His Theta", response.get(8));
			debug.updateField("His U", response.get(9));
			debug.updateField("His V", response.get(10));
			debug.updateField("His Omega", response.get(11));
			
			String ControlString = "{}";	//default control string (observer)
			
			if (!observerMode) 
			{
				//obtain algorithm output
				String acceleration = Double.toString(player.getAcceleration());
				String angacceleration = Double.toString(player.getAngularAcceleration());
				ControlString = "{" + acceleration + "," + angacceleration + "}";
			}
	
			//send algorithm output to server
			outputToServer.print(ControlString);
			outputToServer.flush();
			PursuitClient.print("Sent data: " + ControlString);		
		
		}	//end while(true) loop
		
		//Close socket and streams
		try 
		{
			inputFromServer.close();
			outputToServer.close();
			pursuitSocket.close();
			PursuitClient.print("Streams and socket closed");
		}
		catch (Exception e) 
		{	PursuitClient.print("Error closing streams or socket");	}
    }

	/**
	 * the following chunk reads response from server. I may be overcomplicating 
	 * things but it seems that any sort of readLine() of getLine() method produces 
	 * infinitesimal blocking because of the lack of an endline character. 
	 * So, I will have to resort to primitively reading char by char.
	 * @param reader The BufferedReader to read from
	 * @return The response from the server in String form. 
	 */
    private String ServerRead(BufferedReader reader) 
    {
		//PursuitClient.print("Initiating server response check...");
		
		StringBuffer serverResponse = new StringBuffer();
		try 
		{
			while (!(reader.ready())) 
				{} //if inputFromServer is not ready, blank loop (wait till it's ready)
			if (reader.ready()) 
			{
				//read the server response char by char into StringBuffer
				while (reader.ready()) 
						serverResponse.append((char)reader.read());
				PursuitClient.print("Response From Server: " + serverResponse.toString());
			}
			else PursuitClient.print("Error: inputFromServer is not ready");
		}
		catch (IOException ioe) 
		{	PursuitClient.print(ioe.getMessage());	}
		return serverResponse.toString();
    }
    
    /**
     * Splits string into separate fields with given delimiter (',').
     * @param in The String to be split
     * @param delim The delimiter
     * @param start The index of the string to start at
     * @param end The index of the string to end at
     * @return
     */
    private ArrayList<String> splitString(String in, char delim, int start, int end) 
    {
    	//if end is greater than the input string length, screw it. Bug out.
    	if (end > in.length()) 
    		return null;
    	
    	ArrayList<String> out = new ArrayList<String>();
    	int indexFirst = start;
    	
    	for (int indexCurrent = 0; indexCurrent <= end; indexCurrent++) 
    	{
    		if (in.charAt(indexCurrent) == delim) 
    		{
    			out.add(in.substring(indexFirst, indexCurrent - 1));
    			indexFirst = indexCurrent + 1;
    		}
    	}
    	out.add(in.substring(indexFirst, end));
    	return out;
    }
    
    //the usual accessors and mutators
    public void setServerIP(String IP) {serverIP = IP;}
    public void setServerPort(String port) {serverPort = Integer.parseInt(port);}
    public void setPlayerName(String name) {playerName = name;}
    public void setGameName(String name) {gameName = name;}
    public void toggleObserverMode() {observerMode = !observerMode;}
    public String getServerIP() {return serverIP;}
    public String getServerPort() {return Integer.toString(serverPort);}
    public String getPlayerName() {return playerName;}
	public String getGameType() {return gameType;}
	public String getGameName() {return gameName;}
	public String getPlayerRole() {return player.getRole();}
	public boolean getObserverMode() {return observerMode;}
}